#! bash
# bash completion for the `rake` command.
#
# Copyright (c) 2008 Daniel Luz <dev at mernen dot com>.
# Distributed under the MIT license.
# http://mernen.com/projects/completion-ruby
#
# To use, source this file on bash:
#   . completion-rake

__rake() {
  # $2 returns just the part after the last colon
  local cur=${COMP_WORDS[$COMP_CWORD]}
  local prev=$3
  COMPREPLY=()

  case $prev in
  -f | --rakefile | -r | --require)
    # leave COMPREPLY blank, let the default handle it
    return;;
  -I | --libdir | -R | --rakelibdir)
    _filedir -d
    # suffix all entries with a slash
    COMPREPLY=("${COMPREPLY[@]/%//}")
    return;;
  *)
    if [[ $cur == -* ]]; then
      local options="
        -C --classic-namespace -D --describe -n --dry-run -h --help
        -I --libdir -N --nosearch -P --prereqs -q --quiet -f --rakefile
        -R --rakelibdir -r --require -s --silent -T --tasks -t --trace
        -v --verbose -V --version"
      COMPREPLY=($(compgen -W "$options" -- "$cur"))
    elif [[ $cur == *'['* ]]; then
      # parameter completion (using default compgen)
      # $cur: example[param1,param2
      # $args: param1,param2
      local args=${cur#*[}
      # $last_arg: param2
      local last_arg=${args##*,}
      # $prefix: example[param1,
      local prefix=${cur%"$last_arg"}

      # ugh, ugly hack here, but I'm out of ideas
      local old_ifs=$IFS IFS=$'\n'
      COMPREPLY=($(compgen -o default -- "$last_arg"))
      local IFS=$old_ifs
      # escape space characters...
      COMPREPLY=("${COMPREPLY[@]// /\ }")
      # and prepend the prefix to all completion options
      COMPREPLY=("${COMPREPLY[@]/#/$prefix}")

      __rake_remove_namespaces
      # don't perform usual post-processing
      return
    else
      local rakefile rakedir
      __rake_get_rakefile || return
      COMPREPLY=($(compgen -W "$(__rake_tasks)" -- "$cur"))
    fi
  esac

  __rake_remove_namespaces

  # append a space to all options...
  COMPREPLY=("${COMPREPLY[@]/%/ }")

  # ...and remove [parameters] from tasks
  COMPREPLY=("${COMPREPLY[@]/[*/[}")
}

# bash treats ":" as a path separator, borking our completion on
# namespaced tasks. This function removes whatever the user already typed
# until the last colon.
__rake_remove_namespaces() {
  if [[ $cur == *:* ]]; then
    local prefix=${cur%:*}:
    COMPREPLY=("${COMPREPLY[@]#"$prefix"}")
  fi
}

# Finds the appropriate rakefile, given the arguments in the command-line and
# the current directory.
#
# Writes to `$rakefile` the path of the file, and sets `$rakedir` if the
# command must be run from a different directory.
#
# Since this function sets new variables, it must NOT run on a subshell.
#
# Note that `$rakefile` is also NOT guaranteed to be absolute!
__rake_get_rakefile() {
  # look for --rakefile or -f
  # $rakedir is not defined in this case, regardless of where $rakefile lives
  local i
  for ((i=0; i <= $COMP_CWORD - 1; ++i)); do
    local arg=${COMP_WORDS[$i]}

    case $arg in
    --rakefile | -f)
      rakefile=${COMP_WORDS[$(($i + 1))]}
      return;;
    --rakefile=* | -f=*)
      rakefile=${arg#*=}
      return;;
    esac
  done

  # look for rakefiles in the directory hierarchy
  # note that the command should run on the context of the directory where
  # the rakefile is found, so $rakedir has to be set
  local fn
  pushd . &>/dev/null
  while true; do
    for fn in rakefile Rakefile; do
      if [[ -f $fn ]]; then
        rakedir=$(pwd)
        rakefile=$rakedir/$fn
        popd &>/dev/null
        return
      fi
    done
    cd ..
    [[ $(pwd) == / ]] && break
  done

  popd >/dev/null
  return 1
}

# Outputs the list of possible tasks for the given rakefile and rakedir,
# one task per line.
#
# Parameters can be passed either as environment variables `$rakefile` and
# `$rakedir` or via positional arguments:
#     (__rake_tasks <rakefile> [rakedir])
__rake_tasks() {
  local rakefile=${1:-$rakefile}
  local rakedir=${2:-$rakedir}
  [[ -f $rakefile ]] || return 1
  rakefile=$(ruby -e 'puts File.expand_path(ARGV[0])' -- "$rakefile")

  local cachedir=$HOME/.local/share/completion-cache
  local cachefile=$cachedir/rake
  # uncomment the following lines if you prefer individual caches:
##  local cachedir=$HOME/.local/share/completion-cache/rake
##  local cachefile=$cachedir/${rakefile//\//%}

  local tasks
  if [[ $cachefile -nt $rakefile &&
        $(head -n 1 -- "$cachefile") == $rakefile ]]; then
    tail -n +2 -- "$cachefile"
  else
    [[ -d $rakedir ]] && cd "$rakedir"
    local rake_cmd=${rake_cmd:-rake}
    tasks=$("$rake_cmd" --rakefile "$rakefile" --silent --tasks 2>/dev/null |
            awk '{ print $2 }')
    if [[ $tasks ]]; then
      # attempt to ensure $cachedir exists and is a directory
      # before writing to $cachefile
      ([[ -f $cachedir ]] && rm -- "$cachedir";
       mkdir -p -- "$cachedir" &&
       echo "$rakefile"$'\n'"$tasks" >"$cachefile") 2>/dev/null
      echo "$tasks"
    fi
  fi
}

complete -F __rake -o default -o nospace rake rake1.8 rake1.9
# vim: ai ft=sh sw=2 sts=2 et
